package p.ithorns.framework.common.utils;

import org.apache.commons.codec.digest.Blake3;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import p.ithorns.framework.common.codec.MD5Codec;
import p.ithorns.framework.common.time.TimeUtil;
import p.ithorns.framework.core.constants.FileConsts;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.regex.Matcher;

/**
 * Description:
 * <p>FileUtil</p>
 * Created By Ht_L. 2018/11/7-21:34
 */
public class FileUtil {

    private final static Logger log = LoggerFactory.getLogger(FileUtil.class);

    private static final String ROOT_DIR = System.getProperty("user.dir");

    private static final String SEPARATOR = System.getProperty("file.separator");

    private static final String SECONDARY_DIRECTORY = "..";

    private static final String FILE_PROTO_PREFIX = "file:";

    public static String newName(String originName) {
        try {
            String timestamp = String.valueOf(TimeUtil.timestamp());
            String md5 = MD5Codec.md5(originName, timestamp);
            return md5.substring(0, 16);
        } catch (Exception e) {
            log.warn(e.getMessage());
            return originName;
        }
    }

    public static String getBaseDir() {
        /*try {
            String pathPrefix = ResourceUtils.getURL("classpath:").getPath();
            File file = new File(pathPrefix);
            if(!file.exists())
                file = new File("");
            return file.getAbsolutePath()+ "/";
        } catch (Exception e) {
            log.warn("USER_DIR in classpath is not found! will be set in 'user.dir'");
            return System.getProperty("user.dir") + "/";
        }*/

        return System.getProperty("user.dir") + "/";
    }

    /**
     * 获取文件后缀
     *
     * @param fileName
     * @return
     */
    public static String getSuffix(String fileName) {
        return fileName.substring(fileName.lastIndexOf("."));
    }

    /**
     * 生成新的文件名
     *
     * @param suffix - 文件扩展名
     * @return
     */
    public static String randomFileName(String suffix) {
        return UUID.randomUUID().toString().replace("-", "") + suffix;
    }

    public static String renameToUUID(String fileName) {
        int hashCodeV = UUID.randomUUID().toString().hashCode();
        //有可能是负数
        if (hashCodeV < 0) {
            hashCodeV = -hashCodeV;
        }
        return String.format("%015d", hashCodeV) + "." + fileName.substring(fileName.lastIndexOf(".") + 1);
    }

    /**
     * 循环删除目录以及目录下的文件
     *
     * @param filePath
     */
    public static void delFiles(String filePath) {
        if (filePath == null) {
            return;
        }
        File f = new File(filePath);
        delFile(f);
        f.delete();
    }

    /**
     * 递归方法  delFiles 所调用
     *
     * @param f
     */
    private static void delFile(File f) {
        if (f == null) {
            return;
        }
        if (f.isDirectory()) {
            File[] fs = f.listFiles();
            for (File fstmp : fs) {
                delFile(fstmp);
            }
        } else {
            f.delete();
        }
    }

    /**
     * 获取指定相对路径在当前项目下的绝对路径
     *
     * @param path -相对路径
     * @return Path 当前项目下的绝对路径
     */
    public static Path getAbsPathOfProject(String path) {
        // 1. Spring下可使用这个方法  ResourceUtils.getURL(path).getPath(). 区别：尾缀是否带"/"
        return getAbsPathOfProject(Paths.get(path));
    }

    public static Path getAbsPathOfProject(Path path) {
        // 已经是绝对路径，直接返回
        if (path.startsWith(ROOT_DIR)) {
            return path;
        }

        // 是否以"/"开始
        if (path.startsWith(SEPARATOR)) {
            return Paths.get(ROOT_DIR + path);
        }
        return Paths.get(ROOT_DIR + SEPARATOR + path);
    }

    /**
     * 获取带本地文件协议的文件目录路径
     */
    public static String getDirUriWithProto(String path) {
        return getFileUriWithProto(path) + SEPARATOR;
    }


    /**
     * 获取带本地文件协议的文件路径
     */
    public static String getFileUriWithProto(String path) {
        return FILE_PROTO_PREFIX + getAbsPathOfProject(path);
    }

    public static String getFilePath(String path) {
        return ROOT_DIR
                + SEPARATOR
                + StringUtil.replaceAll(path, "\\/", Matcher.quoteReplacement(SEPARATOR));
    }


    /**
     * 16进制转换成ascii
     */
    public static String toAscii(String value) {
        StringBuilder output = new StringBuilder();
        for (int i = 0; i < value.length(); i += 2) {
            String str = value.substring(i, i + 2);
            output.append((char) Integer.parseInt(str, 16));
        }
        return output.toString();
    }

    /**
     * 递归截取固定开头字符串
     */
    public static String subString(String s, String str) {
        if (s.startsWith(str)) {
            s = s.substring(2);
            s = subString(s, str);
        }
        return s;
    }

    /**
     * 读取上传文件
     *
     * @param file      -文件
     * @param fileTypes -文件类型
     * @return InputStream -输入流
     */
    public static InputStream readMultipartFile(MultipartFile file, List<String> fileTypes) throws Exception {
        if (file == null || file.isEmpty()) {
            throw new Exception("FILE_NOT_EXIST");
        }
        String fileName = file.getOriginalFilename();
        if (StringUtils.isBlank(fileName)) {
            throw new Exception("FILE_NAME_NOT_EMPTY");
        }
        // 判断文件类型
        if (!CollectionUtil.isEmpty(fileTypes)) {
            boolean flag = false;
            for (String e : fileTypes) {
                if (fileName.endsWith(e)) {
                    flag = true;
                    break;
                }
            }
            if (!flag) {
                throw new Exception("FILE_FORMAT_ERROR");
            }
        }
        try {
            return file.getInputStream();
        } catch (IOException e) {
            throw new Exception("FILE_UPLOAD_ERROR");
        }
    }

    /**
     * 读取Excel文件
     *
     * @param excel -文件
     * @return InputStream -输入流
     */
    public static InputStream readExcelFile(MultipartFile excel) throws Exception {
        List<String> xlsType = Arrays.asList(FileConsts.EXCEL_TYPE_SUFFIX_XLS, FileConsts.EXCEL_TYPE_SUFFIX_XLSX);
        return readMultipartFile(excel, xlsType);
    }


    /**
     * 删除文件
     *
     * @param target -目标文件
     */
    public static void delete(Path target) {
        if (!Files.exists(target)) {
            return;
        }

        try {
            Files.walkFileTree(
                    target,
                    new SimpleFileVisitor<Path>() {
                        @Override
                        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                            // 先去遍历删除文件
                            Files.delete(file);
                            return FileVisitResult.CONTINUE;
                        }

                        @Override
                        public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                            // 再去遍历删除目录
                            Files.delete(dir);
                            return FileVisitResult.CONTINUE;
                        }

                    }
            );
            log.info("删除文件目录: {}", target);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 删除文件
     *
     * @param target -目标文件名
     */
    public static void delete(String target) {
        delete(getAbsPathOfProject(target));
    }


    public static String getTailName(String filePath) {
        // 文件名 d:/test/files/a.txt ,取出最终的文件名 a.txt
        if (StringUtil.isNotBlank(filePath)) {
            int index = filePath.lastIndexOf("/");
            if (index != -1) {
                return filePath.substring(index + 1);
            }
        }
        return filePath;
    }

    /**
     * 保存文件
     *
     * @param file           -上传文件
     * @param folderPath     -目标目录
     * @param targetFileName -指定文件名
     * @return String
     */
    public static String store(MultipartFile file, Path folderPath, String targetFileName) {
        // 不存在，则需要创建
        if (!Files.exists(folderPath)) {
            folderPath.toFile().mkdirs();
        }

        if (Objects.isNull(targetFileName) || targetFileName.trim().length() == 0) {
            targetFileName = file.getOriginalFilename();
        }
        final String fileName = folderPath + File.separator + targetFileName;
        try {
            InputStream inputStream = file.getInputStream();
            return store(inputStream, fileName);
        } catch (IOException e) {
            delete(fileName);
            throw new RuntimeException("Could not store files " + fileName + ". Please try again!", e);
        }
    }


    /**
     * 保存文件
     *
     * @param is       -文件流
     * @param filePath -文件路径
     * @return String
     * @throws IOException -
     */
    public static String store(InputStream is, Path filePath) throws IOException {
        // 检查文件名是否包含非法字符
        String fileName = filePath.toString();
        if (fileName.contains(SECONDARY_DIRECTORY)) {
            throw new RuntimeException("Sorry! Filename contains invalid path sequence " + fileName);
        }
        // 不存在，则需要创建
        createDirIfAbsent(filePath);
        Files.copy(is, getAbsPathOfProject(fileName), StandardCopyOption.REPLACE_EXISTING);
        return fileName;
    }


    /**
     * 存储文件
     *
     * @param target      -目标文件
     * @param inputStream -流数据
     * @throws IOException -
     */
    private void storeUseChannel(InputStream inputStream, Path target) throws IOException {
        try (ReadableByteChannel readChannel = Channels.newChannel(inputStream);
             FileChannel targetChannel = FileChannel.open(target, StandardOpenOption.CREATE,
                     StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
            // 分配缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(8196);
            while (readChannel.read(buffer) != -1) {
                buffer.flip(); // 切换为读模式
                targetChannel.write(buffer);
                buffer.clear(); // 清空缓冲区
            }
            log.info("存储文件： {}", target);
        }
    }

    /**
     * 存储文件
     *
     * @param bytes -文件字节数组
     * @author 2023-06-03 16:03 by Ht.L
     */
    public static void store(byte[] bytes, Path path) throws IOException {
        // 检查文件名是否包含非法字符
        if (path.toString().contains("..")) {
            throw new RuntimeException("Sorry! Filename contains invalid path sequence: " + path);
        }

        try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
            ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
            fileChannel.write(byteBuffer);
        }
    }

    /**
     * 保存文件
     *
     * @param is       -文件流
     * @param fileName -文件名
     * @return String
     * @throws IOException -
     */
    public static String store(InputStream is, String fileName) throws IOException {
        // 检查文件名是否包含非法字符
        if (fileName.contains("..")) {
            throw new RuntimeException("Sorry! Filename contains invalid path sequence " + fileName);
        }
        Files.copy(is, getAbsPathOfProject(fileName), StandardCopyOption.REPLACE_EXISTING);
        return fileName;
    }

      /**
     * 创建目录
     *
     * @param path -目录路径
     */
    public static void createDirIfAbsent(Path path) {
        if (Files.notExists(path)) {
            try {
                Files.createDirectory(path);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 创建目录
     *
     * @param filePath -文件路径
     * @return File
     */
    public static File createFile(String filePath) {
        Path absPath = Paths.get(filePath).toAbsolutePath().normalize();
        try {
            Path parent = absPath.getParent();
            if (Files.notExists(parent)) {
                Files.createDirectory(parent);
            }

            Files.createFile(absPath);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return absPath.toFile();
    }

    public static String getBlake3(InputStream stream) {
        byte[] bytes = read(stream);
        return getBlake3(bytes);
    }

    public static String getBlake3(byte[] bytes) {
        // commons-codec实现
        byte[] hash = new byte[16];
        Blake3.initHash().update(bytes).doFinalize(hash);
        return bytesToHex(hash);
//
//        //  org.bouncycastle.crypto.digests.Blake3Digest 实现
//            Blake3Digest digest = new Blake3Digest();
//            digest.update(bytes, 0, bytes.length);
//            byte[] hash = new byte[digest.getDigestSize()];
//            digest.doFinal(hash, 0);
//            return bytesToHex(hash);
    }

    private static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }


    private static byte[] read(InputStream is) {
        try (ByteArrayOutputStream os = new ByteArrayOutputStream();) {
            byte[] buffer = new byte[4096];
            int length;
            while ((length = is.read(buffer)) != -1) {
                os.write(buffer, 0, length);
            }
            os.flush();
            return os.toByteArray();
        } catch (Exception e) {
            // return new byte[0];
            throw new RuntimeException("读取流数据失败.", e);
        }
    }

}
